import module namespace tntx = "http://www.functx.com" at "regexps.xq";

declare namespace xst = "http://code.google.com/p/xstag/ns/2011/";
declare namespace tnt = "http://code.google.com/p/xstag/ns/2011/impl";

declare namespace xsl="http://www.w3.org/1999/XSL/Transform";



(:declare boundary-space preserve;:)

(: Saxon-specific:)
declare namespace saxon="http://saxon.sf.net/";
declare option saxon:output "omit-xml-declaration=yes";
(: END: Saxon-specific:)

(:declare variable $INPUT := "../../example/src/books.html";:)
(:declare variable $INPUT := "../../test/src/BooksTagDoc.xml";:)
(:declare variable $INPUT := "../../test/out/Hand2UseCallLib.xstag";:)
(:declare variable $INPUT := "../../example/src/feed-booklet.fo";:)
declare variable $INPUT external;

(: constants :)
declare variable $brackets-regexp := "^[^\{]*\{[^\{].*[^\}]\}[^\}]*";
declare variable $pure-var-regexp := "^\{\$.+\}$";

declare function tnt:error($reason as xs:string, $error-obj as item()*) {
  error(xs:QName('xst:err'), $reason, $error-obj)
};

declare function tnt:error($reason as xs:string) {
  error(xs:QName('xst:err'), $reason)
};

(: Helper functions :)
declare function tnt:get-prefix($name as xs:string) as xs:string {
  substring-before($name, ':')
};

declare function tnt:get-local-name($name as xs:string) as xs:string {
  substring-after($name, ':')
};

declare function tnt:is-last-node($node as node()) as xs:boolean{
  (count($node/following-sibling::node()[tnt:is-right-node(.)]) = 0)
};

(::END - Helper functions :)

(: This function gets tag defs from all imported tag libs and
then wrapps them into tnt:tag element preserving the URI and name of tags:)
declare function tnt:get-tag-defs($tag-libs-paths as xs:anyURI*) {
  for $path in $tag-libs-paths return
    let $tag-defs := doc($path)/xst:taglib/xst:tag
    for $tag-def in $tag-defs return
      if(empty($tag-def/@name)) then
        tnt:error("xst:tag elements should have a 'name' attribute", $tag-def)
      else
        let $full-name := $tag-def/@name/string(.)
        let $ns-uri := $tag-def/namespace-uri-for-prefix(tnt:get-prefix($full-name), .)
        let $tag-name := tnt:get-local-name($full-name)
        return 
          <tnt:tag uri="{$ns-uri}" name="{$tag-name}">
            {$tag-def}
          </tnt:tag>          
};

declare function tnt:get-tag-defs-recursively($tag-libs-paths as xs:anyURI*) {
  let $distinct-paths := distinct-values(
    for $path in $tag-libs-paths return     
      let $doc := doc($path)
      let $imports := $doc/*/xst:import/@href
      let $tag-libs-paths-new := $imports/resolve-uri(., document-uri($doc))
      return $tag-libs-paths-new
  )
  return
  (tnt:get-tag-defs($tag-libs-paths),
    if(not(empty($distinct-paths))) then 
      tnt:get-tag-defs-recursively($distinct-paths)
    else
      ()
  )
};

declare function tnt:get-tag-def($tag-defs, $el as element()) as element()? {
  $tag-defs[./@name = $el/local-name() and ./@uri = $el/namespace-uri()]
};

declare function tnt:get-tag-def-by-name($tag-defs, $name as xs:string, $call-el as element(xst:call)) as element()? {
  let $pr := tnt:get-prefix($name)
  let $local-name := tnt:get-local-name($name)
  return
    $tag-defs[./@name = $local-name and ./@uri = namespace-uri-for-prefix($pr, $call-el)]
};

declare function tnt:get-tag-def-by-name-and-uri($tag-defs, $name as xs:string, $uri as xs:string) as element()? {
  let $pr := tnt:get-prefix($name)
  let $local-name := tnt:get-local-name($name)
  return
    $tag-defs[./@name = $local-name and ./@uri eq $uri]
};


(: TODO: improve better value fetching and error handling when value exists as a child:)
declare function tnt:get-variable-value($tag-defs as element(tnt:tag)*, $var as element(xst:variable), $doc as element()) as item()* {
  if($var/@select) then data($var/@select) else 
  if(not(empty($var/element()))) then tnt:process-tag-doc-element($tag-defs, $var/element()[1], $doc) else
    concat("'", normalize-space($var/text()), "'")
};


declare function tnt:wrap-string($s as xs:string, $as as xs:string?) as xs:string {
(: TODO: escape quotes and curly bracets :)
  if(starts-with($as, "xs:integer")) then
    $s
  else    
    concat("'", $s, "'")
};

declare function tnt:trim ($s as xs:string?) as xs:string {       
  replace(replace($s,'\s+$',''),'^\s+','')
};


declare function tnt:process-variable($tag-defs as element(tnt:tag)*, $el as element(xst:variable), $doc as element()) {
  ("&#x0A; let ", concat("$", string($el/@name)), if($el/@as) then concat(" as ", string($el/@as)) else (), 
    " := ",tnt:get-variable-value($tag-defs, $el, $doc), " return ")
};


declare function tnt:is-right-node($node as node()) {
  not($node instance of comment()) and (not($node instance of text()) or fn:matches(string($node),"\S"))
};


declare function tnt:params2xquery($params as element(tnt:params)) {
  for $p in $params/tnt:param[not(starts-with(normalize-space(@as), "handler"))] return
    let $as-part := 
        if($p/@as) then concat (" as ", string($p/@as)) else ()
    let $values :=  
        $p/tnt:value[1]/node()[tnt:is-right-node(.)] 
    return
    (concat("let $", string($p/@name)), $as-part, " := ", 
      tnt:wrap-into-type-constructor($values, $as-part)
    , 
    " return &#x0A;")  
};

declare function tnt:wrap-into-type-constructor($items, $as-part) {
  if(empty($as-part)) then $items 
  else 
    if(matches(tnt:trim($as-part), "string\??$"))
      then ("xs:string(", $items, ")")
    else if(matches(tnt:trim($as-part), "integer\??$")) then
      ("xs:integer(", $items, ")")
    else
      $items
};


declare function tnt:guess-wrap-text($text as xs:string, $as-part as xs:string?) {
      if(matches($as-part, "string\??$") 
        and not(matches($text, "\$")) and not(matches($text, "^'.*'$")) 
        and not(matches($text, '^".*"$'))) then
        concat("'", $text, "'")
      else
        $text
};



declare function tnt:copy($node as node()) as node() {
   if($node instance of element()) then 
   element {node-name($node)}
      {$node/@*,
          for $child in $node/node()[tnt:is-right-node(.)]
              return
               if ($child instance of element())
                 then tnt:copy($child)
                 else $child
      }
    else $node 
};

declare function tnt:passed-params($wrapped-params as element(tnt:params), $tag-def as element(tnt:tag), 
                                   $tag as element()) as xs:string {
  let $params := for $name in $wrapped-params/tnt:param[not(starts-with(@as, "handler"))]/@name/string(.) return concat("$", $name)
  let $template-params := tnt:get-function-params-value-template($tag-def/xst:tag, $tag)
  return 
  string-join(($params, $template-params[not(. = $params)]), ",")
};

     
declare function tnt:make-tag-function-body($tag-defs as element(tnt:tag)*, $tag-def as element(), 
                                            $tag-call as element(), $doc as element()) {
  let $tag-ns-uri := $tag-def/@uri
  (:let $wrapped-params := tnt:wrap-tag-params($tag-def, $tag-call, $doc):)
    let $seq := ( 
      "(",
      for $st in $tag-def/xst:tag/node()[not(. instance of element(xst:param)) and tnt:is-right-node(.)] return
        (
        if($st instance of text()) then
          tnt:get-text-exp($st, true()) 
        else if($st instance of element()) then
          tnt:process-tag-def-element($tag-defs, $st, $tag-call, true(), $doc)          
        else          
          (tnt:copy($st))      
        ,
        tnt:put-bracket-or-comma2($st)
       ),
      ")"
    )
    
    return 
      $seq       
};



(:
TNT: check this change
:)
declare function tnt:put-bracket-or-comma($n as node()) {
  if(tnt:is-last-node($n)) then ")" else ","
};


declare function tnt:put-bracket-or-comma2($child as node()) {
          if(tnt:is-expression-context($child)) then 
            if(tnt:is-last-node($child)) then 
            tnt:put-brackets2($child)
            else if($child instance of element(xst:variable)) then () else  
            ","
          else ()
};         


declare function tnt:get-full-tag-name($el as element()) as xs:string {
  if(tnt:is-explicit-tag-call($el)) then
    string($el/@tag)
  else
    string($el/node-name(.))
};
(:wrap parameters that are passed into tag call:)
declare function tnt:wrap-tag-params($tag-defs as element(tnt:tag)*, $tag-def as element(tnt:tag), $tag as element(), $doc as element()) {
  let $tag-ns-uri := $tag-def/@uri 
  let $tag-local-name := $tag-def/@name
  return
  <tnt:params tag-call="{tnt:get-full-tag-name($tag)}" tag-uri="{$tag-ns-uri}">
  {  
    for $param in $tag-def/xst:tag/xst:param return
      <tnt:param name="{$param/@name}" as="{$param/@as}">
      {
        let $tag-param := $tag/*[./namespace-uri() = $tag-ns-uri and ./local-name() = $param/@name] 
        let $att-param := $tag/@*[./name() = $param/@name]
        return
        (
        if(not(empty($tag-param))) then
          <tnt:value>
          {
          if($tag-param/@select) then tnt:get-text-exp-for-params-wrap($tag-param/@select/string(.), $param/@as/string(.))
          else
            tnt:process-children-tag-def-element($tag-defs, $tag-param, $tag, true(), $doc)          
          }
          </tnt:value>
        else (),
        if(not(empty($att-param))) then
          <tnt:value>
          {         
            tnt:get-text-exp-for-params-wrap($att-param/string(.), $param/@as/string(.))            
          }
          </tnt:value>
        else (),
        if($param/@implicit = "true" and not(tnt:is-explicit-tag-call($tag))) then
          <tnt:value>
          {
            (:$tag/node()[not(namespace-uri(.) eq namespace-uri(<xst:dummy/>)) and tnt:is-right-node(.)]:)
            tnt:process-children-tag-def-element($tag-defs, $tag, $tag, true(), $doc)
          }
          </tnt:value>
        else 
        (),
        
        (: wrapping parameters from xst:call :)
        if(tnt:is-explicit-tag-call($tag)) then
         let $call-param := $tag/*[string(@name) eq string($param/@name)] return
            
         if(not(empty($call-param))) then

          (
           if($call-param/@select) then
          <tnt:value>
          {
            tnt:get-text-exp-for-params-wrap($call-param/@select/string(.), $param/@as/string(.))
          }
          </tnt:value>
          else ()
          ,
          let $nodes := $call-param/node()[tnt:is-right-node(.)] return
          if(not(empty($nodes))) then
            <tnt:value>
              {
              (:$nodes:)
                tnt:process-children-tag-def-element($tag-defs, $call-param, $tag, true(), $doc)
              }
            </tnt:value>
          else ()
          )
         else 
          ()
        else
        ()
        ,
      (: Default parameters from tag lib :)
        <tnt:value> 
        {
          if($param/@select) then 
            tnt:get-text-exp-for-params-wrap($param/@select/string(.), $param/@as/string(.))
          else
          (:$param/node()[tnt:is-right-node(.)]:)
            tnt:process-children-tag-def-element($tag-defs, $param, $tag, true(), $doc) 
        }
        </tnt:value>
        )
      }
      </tnt:param>
  }    
  </tnt:params>
};

declare function tnt:wrap-params($tag-defs as element(tnt:tag)*, $cur-tag-call as element(), $params as element()*, $doc as element()) as element() {
  <tnt:params>
  {  
    for $param in $params return
      <tnt:param name="{$param/@name}" >
      {
        ($param/@as,
        
          <tnt:value> 
          {
            if($param/@select) then tnt:get-text-exp-for-params-wrap($param/@select/string(.), $param/@as/string(.))
            else
              tnt:process-children-tag-def-element($tag-defs, $param, $cur-tag-call, true(), $doc)
(:              for $n in $param/node()[tnt:is-right-node(.)] return
                if($n instance of element()) then
                  tnt:process-tag-def-element($tag-defs, $n, $cur-tag-call, false(), $doc)   
                else 
                  $n                
:)     
(:                 $n:)
          }
          </tnt:value>
        )
      }
      </tnt:param>
  }    
  </tnt:params>
};

declare function tnt:is-expression-context($n as node()) as xs:boolean {
  not(empty($n/preceding-sibling::element(xst:variable))) 
  or
  (: this is when this is a first child and the parent is variable :)  
  (empty($n/preceding-sibling::*) and $n/.. instance of element(xst:variable))
  or
  $n/parent::* instance of element(xst:tag)  
};

declare function tnt:is-var-after($n as node()) as xs:boolean {
  not(empty($n/following-sibling::element(xst:variable)))  
};

declare function tnt:get-text-exp($t as xs:string, $wrap as xs:boolean) {
 (:TODO: use regexps:) 
 let $nt := normalize-space($t)
 return
  if(matches($t, $brackets-regexp, "s")) then
    (: If it is just a pure var expression, just return it:)
    if(matches($nt, $pure-var-regexp, 's')) then
       if(not($wrap)) then $nt else substring($nt, 2, string-length($nt) - 2)
    else
    let $seq := (<wrap>{$t}</wrap>, "/node()") return
      if(not($wrap)) then ("{", $seq, "}") else $seq
  else
     if($wrap) then 
     concat("'", $nt, "'")
     else $t    
};

declare function tnt:get-text-exp-for-params($t as xs:string, $wrap as xs:boolean) {
 (:TODO: use regexps:) 
 let $nt := normalize-space($t)
 return
  if(matches($t, $brackets-regexp, "s")) then
    if(matches($nt, $pure-var-regexp, 's')) then
      if(not($wrap)) then $nt else substring($nt, 2, string-length($nt) - 2)
      else

      let $seq := (<wrap>{$t}</wrap>, "/node()") return
        $seq
  else
    $t    
};

declare function tnt:get-text-exp-for-params-wrap($t as xs:string, $as-part as xs:string?) {
 (:TODO: use regexps:) 
 let $nt := normalize-space($t)
 return
  if(matches($t, $brackets-regexp, "s")) then
    if(matches($nt, $pure-var-regexp, 's')) then      
      substring($nt, 2, string-length($nt) - 2)
      else

      let $seq := (<wrap>{$t}</wrap>, "/node()") return
        $seq
  else
    tnt:guess-wrap-text($nt, $as-part)
};


declare function tnt:put-brackets($n as node()) as xs:string+ {
  (for $i in 1 to count($n/preceding-sibling::element(xst:variable)) return ")",
  "}")
};

declare function tnt:put-brackets-tag-def-element($n as node(), $is-seq as xs:boolean) as xs:string* {
  (for $i in 1 to count($n/preceding-sibling::element(xst:variable)) return ")",
  if($is-seq) then () else "}")
};

declare function tnt:put-brackets2($n as node()) as xs:string* {
  (for $i in 1 to count($n/preceding-sibling::element(xst:variable)) return ")")
};

declare function tnt:process-attribute($a as element(xst:attribute)) {
  concat ("attribute ", " ", $a/@name, " {", $a/@select, "}")
};

declare function tnt:process-xst-content($e as element(xst:content)) {
  $e/@select/string(.)
};

declare function tnt:process-xst-text($e as element(xst:text)) {
  concat("'", string($e), "'")
};


declare function tnt:wrap-context-sensitive($exp as item(), $orig-node as node()) { 
  if(tnt:is-expression-context($orig-node)) then
    if(tnt:is-last-node($orig-node)) then
      ($exp, ")") else ($exp, ",")
  else
    ("{", $exp, "}")
};

declare function tnt:wrap-context-sensitive2($exp as item()*, $orig-node as node()) { 
  if(tnt:is-expression-context($orig-node)) then
      ($exp)
  else
    ("{", $exp, "}")
};

(: TODO make use of this method in other places :)
declare function tnt:is-explicit-tag-call($el as element()) {
  ($el instance of element(xst:call) and not(empty($el/@tag)))
};

(: Substitutes a tag call by let statements (parameters of a tag call) and the call to the tag function:)
declare function tnt:process-tag-call($tag-defs, $tag-def as element(tnt:tag), $tag-call as element(), $doc as element()) {
  let $tag-ns-uri := $tag-def/@uri
  let $wrapped-params := tnt:wrap-tag-params($tag-defs, $tag-def, $tag-call, $doc)
  let $xqparams := tnt:params2xquery($wrapped-params)
  let $vars := for $var in $tag-call/xst:variable return tnt:process-variable($tag-defs, $var, $doc)
  let $count := if(tnt:is-explicit-tag-call($tag-call)) then 
    tnt:count-same-call-element($tag-defs, $tag-call) else tnt:count-same-element($tag-call)
  return 
  ($xqparams, $vars, concat(tnt:get-function-name($tag-call), $count), "(", 
      tnt:passed-params($wrapped-params, $tag-def, $tag-call), ")")
};
    

declare function tnt:process-tag-doc-element($tag-defs, $el as element(), $doc as element()) {
  if($el instance of element(xst:import)) then () else
  if($el instance of element(xst:variable)) then
    ((if(tnt:is-expression-context($el)) then () else '{'), tnt:process-variable($tag-defs, $el, $doc), "(")
  else
  if($el instance of element(xst:attribute)) then
    tnt:wrap-context-sensitive(tnt:process-attribute($el), $el)
  else
  if($el instance of element(xst:content)) then
    tnt:wrap-context-sensitive(tnt:process-xst-content($el), $el)
  else
  if(not(empty(tnt:get-tag-def($tag-defs, $el)))) then
    tnt:wrap-context-sensitive2(tnt:process-tag-call($tag-defs, tnt:get-tag-def($tag-defs, $el), $el, $doc), $el)
  else 
    
  element {node-name($el)}
  {$el/@*[not(namespace-uri(.) eq namespace-uri(<xst:dummy/>))], 
    (
        for $child in $el/node()[tnt:is-right-node(.)] return
          
          (if($child instance of element()) then
            tnt:process-tag-doc-element($tag-defs, $child, $doc)
          else if ($child instance of text()) then tnt:get-text-exp($child, tnt:is-expression-context($child))
          else ('"some_type"') (: TODO: include more types :)
          ,
          (: Logics for putting separators in case of xst:variable -- trickier than in XSLT :)
          if(tnt:is-expression-context($child)) then 
            if(tnt:is-last-node($child)) then 
            tnt:put-brackets($child)
            else if($child instance of element(xst:variable)) then () else  
            ","
          else ()
          )
    )
  }
};

declare function tnt:process-for-each($tag-defs as element(tnt:tag)*, $el as element(xst:for-each), $cur-tag-call as element(), $doc as element()) {
  ("&#x0A;for ", concat("$", $el/@name), " in ", string($el/@select), " return &#x0A;",
    for $child in $el/node()[tnt:is-right-node(.)] return
      if($child instance of element()) then tnt:process-tag-def-element($tag-defs, $child, $cur-tag-call, true(), $doc)
      else
      if($child instance of text()) then
        tnt:get-text-exp($child, tnt:is-expression-context($child))
        
      else 
        $child

)
};

declare function tnt:process-tag-lib-xst-call($tag-defs as element(tnt:tag)*, $st as element(xst:call), 
                                              $tag-def as element(tnt:tag), $tag-call as element()?, $doc as element()) {
  let $wrapped-params := tnt:wrap-tag-params($tag-defs, $tag-def, $tag-call, $doc) return           
          if($st/@handler) then
            (
              tnt:params2xquery(tnt:wrap-params($tag-defs, $tag-call, $st/xst:with-param, $doc))
              ,
              "(", 
                  $wrapped-params/tnt:param[@name=substring-after($st/@handler, '$')]/tnt:value[1]/node()
               ,")"
             ) 
                     
          else
            if($st/@tag) then
              let $tag-def := tnt:get-tag-def-by-name($tag-defs, $st/@tag, $st) 
              return
                tnt:process-tag-call($tag-defs, $tag-def, $st, $doc) 
          else
            tnt:error("Call is not supported")

};

declare function tnt:process-tag-def-element($tag-defs as element(tnt:tag)*, $el as element(), $cur-tag-call as element(), 
                                             $is-seq as xs:boolean, $doc as element()) {
  if($el instance of element(xst:variable)) then
    ((if(tnt:is-expression-context($el) or $is-seq) then () else '{'), tnt:process-variable($tag-defs, $el, $doc), "(")
  else
  if($el instance of element(xst:attribute)) then
    tnt:wrap-context-sensitive(tnt:process-attribute($el), $el)
  else
  if($el instance of element(xst:content)) then
    let $cont := tnt:process-xst-content($el) return
    if($is-seq) then $cont else ("{", $cont, "}")
  else
  if($el instance of element(xst:text)) then
    let $text := tnt:process-xst-text($el)
    return
      if($is-seq) then $text else ("{", $text, "}")    
  else
  if(not(empty(tnt:get-tag-def($tag-defs, $el)))) then
    tnt:wrap-context-sensitive2(tnt:process-tag-call($tag-defs, tnt:get-tag-def($tag-defs, $el), $el, $doc), $el)
  else 
  if($el instance of element(xst:for-each)) then
    tnt:process-for-each($tag-defs, $el, $cur-tag-call, $doc) 
  else
  if($el instance of element(xst:call)) then
    if(tnt:is-explicit-tag-call($el)) then
     tnt:process-tag-lib-xst-call($tag-defs, $el, tnt:get-tag-def-by-name($tag-defs, $el/@tag, $el), (), $doc)
         
    else
  
    let $tag-def-raw := $el/ancestor::element(xst:tag)[1]
    let $tag-def-raw := if(empty($tag-def-raw)) then 
        tnt:error("Implementation error, cannot find a tag definition that uses xst:call...")
    else
      $tag-def-raw
      
    return 
            
    let $tag-def := $tag-defs[tnt:get-local-name($tag-def-raw/@name) eq ./@name and 
          namespace-uri-for-prefix(tnt:get-prefix($tag-def-raw/@name), $tag-def-raw) eq ./@uri] return
    tnt:process-tag-lib-xst-call($tag-defs, $el, $tag-def, $cur-tag-call, $doc)
  else
  
  element {node-name($el)}
  {$el/@*[not(namespace-uri(.) eq namespace-uri(<xst:dummy/>))], 
    (
(:        for $child in $el/node()[not(. instance of element(xst:param)) and tnt:is-right-node(.)] return
          
          (if($child instance of element()) then
            tnt:process-tag-def-element($tag-defs, $child, $cur-tag-call, false())
          else if ($child instance of text()) then tnt:get-text-exp($child, tnt:is-expression-context($child))
          else ('"some_type"') (\: TODO: include more types :\)
          ,
          (\: Logics for putting separators in case of xst:variable -- trickier than in XSLT :\)
          if(tnt:is-expression-context($child)) then 
            if(tnt:is-last-node($child)) then 
            tnt:put-brackets($child)
            else if($child instance of element(xst:variable)) then () else  
            ","
          else ()
          )
:)  
    tnt:process-children-tag-def-element($tag-defs, $el, $cur-tag-call, false(), $doc)
    )
  }
};

declare function tnt:process-children-tag-def-element($tag-defs as element(tnt:tag)*, $el as element(), $cur-tag-call as element(), 
                                                      $is-seq as xs:boolean, $doc as element()) {
        for $child in $el/node()[not(. instance of element(xst:param)) and tnt:is-right-node(.)] return
          
          (if($child instance of element()) then
            tnt:process-tag-def-element($tag-defs, $child, $cur-tag-call, $is-seq, $doc)
          else if ($child instance of text()) then tnt:get-text-exp($child, tnt:is-expression-context($child) or $is-seq)
          else ('"some_type"') (: TODO: include more types :)
          ,
          (: Logics for putting separators in case of xst:variable -- trickier than in XSLT :)
          if(tnt:is-expression-context($child) or $is-seq) then 
            if(tnt:is-last-node($child)) then 
            tnt:put-brackets-tag-def-element($child, $is-seq)
            else if($child instance of element(xst:variable)) then () else  
            ","
          else ()
          )
};


declare function tnt:get-module-import($pref as xs:string, $ns as xs:string, $location as xs:string) as xs:string{
    concat("import module namespace ", $pref, "=", "'", $ns, "' at '", $location, "';&#x0A;") 
};

declare function tnt:get-namespace-decl($pref as xs:string, $ns as xs:string) as xs:string{
    concat("declare namespace ", $pref, "=", "'", $ns, "' ;&#x0A;") 
};

declare function tnt:get-module-imports($doc as element(), $paths as xs:string*) {
  for $path in $paths return
    let $lib := doc($path)/xst:taglib
    let $pref:= tnt:get-prefix($lib/xst:tag[1]/@name)
    let $ns-uri := namespace-uri-for-prefix($pref, $lib)
    let $tagdoc-pref := for $p in in-scope-prefixes($doc) return 
      if(namespace-uri-for-prefix($p, $doc) eq $ns-uri) then $p else ()
    return 
      tnt:get-module-import($tagdoc-pref, $ns-uri, replace($path, "\.xstag$", ".xqy"))
};

(:used for simple XQ without XQU:)
declare function tnt:get-namespace-decls($doc as element(), $paths as xs:string*) {
  for $path in $paths return
    let $lib := doc($path)/xst:taglib
    let $pref:= tnt:get-prefix($lib/xst:tag[1]/@name)
    let $ns-uri := namespace-uri-for-prefix($pref, $lib)
    let $tagdoc-pref := for $p in in-scope-prefixes($doc) return 
      if(namespace-uri-for-prefix($p, $doc) eq $ns-uri) then $p else ()
    return 
      tnt:get-namespace-decl($tagdoc-pref, $ns-uri)
};

declare function tnt:variables-in-scope($el as element()) as xs:string*{
  if($el/ancestor::tnt:params) then tnt:error(concat("implementation error: a node was already detached", node-name($el))) else
  let $var-names := distinct-values(($el/ancestor::*/preceding-sibling::element(xst:variable)/@name/string(.), 
      $el/preceding-sibling::element(xst:variable)/@name/string(.)))
  return (for $var in $var-names return concat('$', $var))
};

(: this function is responsible for getting variables from a tag call that are not used in a handler:)
(: it's really messy it won't work in all cases :( :)
declare function tnt:get-function-params-value-template($tag-def as element(xst:tag), $tag as element()) as xs:string* {
  let $handlers := $tag/descendant-or-self::*[@xst:handles]
  let $strings := for $str in $handlers/@xst:handles/string(.) return tokenize($str, ",\s")
  let $handles-vars := distinct-values(for $s in $strings return if(starts-with($s, "$")) then $s else concat("$", $s)) 
  (:let $select-matches := tntx:get-matches(string-join($tag//*/@select/string(.), " "), "\$[\w]+")
                         [string-length(tnt:trim(.)) ne 0] (\: TODO: make a condition that this variable exists somewhere before:\):)
(:
  let $tag-body-vars-raw := (tntx:get-matches(string($tag), "\$[\w]+")[string-length(tnt:trim(.)) ne 0] 
    ,
    tntx:get-matches(string-join($tag//*/@*/string(.), " "), "\{\$[\w]+\}")(\: from attributes:\)
    , 
    for $m in distinct-values($select-matches) return concat("{", $m, "}")
  ):)
  
  
  (:let $tag-body-vars := for $s in $tag-body-vars-raw return if(string-length(tnt:trim($s)) = 0) then () else $s
  let $tag-body-vars := for $s in $tag-body-vars return substring($s, 2, string-length($s) - 2)
  :)
  let $interest-vars := distinct-values(tnt:variables-in-scope($tag)[not(. = $handles-vars)])
    (:[not(matches(string-join($tag-def/xst:param/@as[starts-with(., "handler")]/string(.), "; "), concat("\", .)))]:)
    (: the last predicate is needed when we call a handler with <xst:with-handler> and it does not contain xst:handles attribute
    VZ : I'm still convinced that it is needed 
    :)
  return
    
    ($interest-vars)
};

declare function tnt:get-function-params($tag-def as element(xst:tag), $tag as element()) as xs:string {
  string-join(
  (: Parameters of the tag def:)
  let $main-vars := (for $p in $tag-def/xst:param return
    if(starts-with($p/@as, "handler")) then
      ()  
    else
      concat("$", $p/@name, if($p/@as) then concat(" as ", $p/@as) else "")
(:   ,
     (\: params of value templates :\)
     tnt:get-function-params-value-template($tag-def, $tag)   
:)   )
   return ($main-vars, tnt:get-function-params-value-template($tag-def, $tag)
                        [not(some $i in $main-vars satisfies starts-with($i, .))])
                                                
  , ", ")    
};



declare function tnt:get-function-name($tag-call as element()) as xs:string {
  if(tnt:is-explicit-tag-call($tag-call)) then
   (: this is quite hacky, should find a better way... :)
   let $parent-tag := $tag-call/ancestor::element(xst:tag)[1] return
     if(empty($parent-tag)) then
        string($tag-call/ancestor::*/@tag-call)
     else
        concat(tnt:get-prefix($parent-tag/@name), ":", tnt:get-local-name($tag-call/@tag))
     
  else
   string($tag-call/node-name(.))
};

declare function tnt:convert-tag-def-to-function($tag-defs as element(tnt:tag)*, $tag-def as element(tnt:tag), 
                                                 $call-num as xs:string, $tag as element(), $doc as element()) {
  ("declare function ", concat(tnt:get-function-name($tag), $call-num), " (", 
    tnt:get-function-params($tag-def/xst:tag, $tag), ") { &#x0A;", 
(:  content of the tag inside a function goes here:)
    tnt:make-tag-function-body($tag-defs, $tag-def, $tag, $doc)    
    ,
(:  end of the function content:)
  " &#x0A;}; &#x0A;&#x0A;")   
};


declare function tnt:get-module-declaration($taglib as element(xst:taglib)) {
  let $pref := tnt:get-prefix($taglib/xst:tag[1]/@name)
  let $ns-uri := namespace-uri-for-prefix($pref, $taglib)
  return 
    concat ("module ", "namespace ", $pref, " = '", $ns-uri, "';&#x0A;")  
};

declare function tnt:get-tnt-ns-declaration() as xs:string+ {
  ("declare namespace tnt = 'http://code.google.com/p/xstag/ns/2011/impl';&#x0A;",
  "declare namespace xst = 'http://code.google.com/p/xstag/ns/2011/';&#x0A;")
};

declare function tnt:process-tag-lib($doc as document-node()) {
  (:let $doc := doc($input):)
  let $imports := $doc/xst:taglib/xst:import/@href
  let $tag-libs-paths := $imports/resolve-uri(., document-uri($doc))
  let $tag-defs := tnt:get-tag-defs($tag-libs-paths)
  return 
    (
     (: concat(document-uri($doc), ".xq"),:)
      document {
      (
        tnt:get-module-declaration($doc/xst:taglib),
        tnt:get-tnt-ns-declaration(),
        tnt:get-module-imports($doc/xst:taglib, $tag-libs-paths),
        tnt:get-tag-functions($tag-defs, $doc/xst:taglib, $doc/xst:taglib)        
        
      )
    }
    (:,
    
      for $path-tag-lib in $tag-libs-paths return        
          tnt:process-tag-lib($path-tag-lib)        
:)
   )
  
};


declare function tnt:count-same-element($el as element()) as xs:integer {
count($el/ancestor-or-self::*[./namespace-uri() eq $el/namespace-uri() and ./local-name() eq $el/local-name()])
               +
               count($el/preceding-sibling::*[./namespace-uri() eq $el/namespace-uri() and ./local-name() eq $el/local-name()])
};

declare function tnt:count-same-call-element($tag-defs, $el as element(xst:call)) as xs:integer {
count($el/ancestor-or-self::xst:call[tnt:get-tag-def-by-name($tag-defs, ./@tag, .) = tnt:get-tag-def-by-name($tag-defs, $el/@tag, $el)])
               +
               count($el/preceding-sibling::xst:call[tnt:get-tag-def-by-name($tag-defs, ./@tag, .) = tnt:get-tag-def-by-name($tag-defs, $el/@tag, $el)])
};

(: Recursing over all elements and finding tag calls :)
declare function tnt:get-tag-functions($tag-defs as element(tnt:tag)*, $el as element(), $doc as element()) {
  (
  if(not(empty(tnt:get-tag-def($tag-defs, $el)))) then
    tnt:convert-tag-def-to-function($tag-defs, tnt:get-tag-def($tag-defs, $el),        
        string(tnt:count-same-element($el)), $el, $doc
        )
  else
    ()
  ,
  if($el instance of element(xst:call) and $el/@tag) then
    tnt:convert-tag-def-to-function($tag-defs, tnt:get-tag-def-by-name($tag-defs, $el/@tag, $el),        
        string(tnt:count-same-call-element($tag-defs, $el)), $el, $doc
        )
  else 
    ()
  ,
  for $e in $el/* return
    tnt:get-tag-functions($tag-defs, $e, $doc)
  )    
};

declare function tnt:get-saxon-options() {
  (
    "declare namespace saxon='http://saxon.sf.net/'; &#x0A;",
    "declare option saxon:output 'indent=yes'; &#x0A;" 
  )
};


declare function tnt:tag-doc($doc as document-node()) {  
  let $imports := $doc/*/xst:import/@href
  let $tag-libs-paths := $imports/resolve-uri(., document-uri($doc))
  let $tag-defs := tnt:get-tag-defs-recursively($tag-libs-paths)
  
  return
    ( (:concat(document-uri($doc), ".xq"),:)
      document {
      (
        tnt:get-module-imports($doc/*, $tag-libs-paths),
        (:tnt:get-namespace-decls($doc/*, $tag-libs-paths),:)
        tnt:get-tnt-ns-declaration(),
        tnt:get-saxon-options(), (: not necessary but nice :)
        tnt:get-tag-functions($tag-defs, $doc/*, $doc/*),        
        tnt:process-tag-doc-element ($tag-defs, $doc/*, $doc/*)
      )
    }
(:    ,
    
    for $path-tag-lib in $tag-libs-paths return
        tnt:process-tag-lib($path-tag-lib)
    )
:)
  )
};

(:declare function tnt:process-all() {
  (tnt:tag-doc($INPUT))  
};
:)
declare function tnt:main() {
  let $doc := doc($INPUT) return
  if($doc/* instance of element(xst:taglib)) then
    tnt:process-tag-lib($doc)  
  else
    tnt:tag-doc($doc)
};

tnt:main()

